package org.andao.security.rest;

import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;

import org.andao.core.utils.EncryptUtils;
import org.andao.core.utils.ExStringUtils;
import org.andao.core.utils.WrappedRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 用于REST OPEN API的请求签名辅助类.
 *
 * @author Seewo Software - Marco.hu(huzhiguo@cvte.cn)
 *
 */
public class HttpRequestSiningHelper {

	protected static Logger logger = LoggerFactory.getLogger(HttpRequestSiningHelper.class);
	
	private static String DEFAULT_METHOD = "HmacSHA256";//默认算法
	
	/**
	 * 检验请求签名是否与服务端签名一致.
	 * @param request
	 * @param secretKey 服务端签名
	 * @return
	 */
	public static boolean checkRequest(final WrappedRequest request,final String secretKey) {

		if (!hasValidRequestTimeStamp(request)) {//校验时间戳
			return false;
		}

		final String requestSignature = request.getHeader(HmacAttributes.X_HMAC_AUTH_SIGNATURE);//获取用于请求的签名		
		final String method = request.getHeader(HmacAttributes.X_HMAC_AUTH_METHOD);//获取用于签名的算法
		
		if(!ExStringUtils.equalsIgnoreCase(method, DEFAULT_METHOD)){
			logger.warn("不被支持的签名算法:{}",method);
			return false;
		}
		final String[] split = requestSignature.split(":");
		final String sentSignature = split[1];//发送的请求签名 username:singnatureString
		String serverSignatrue =  createRequestSignature(request,secretKey);
		return ExStringUtils.equals(serverSignatrue, sentSignature);
	}

	/**
	 * 校验请求时间戳.
	 * 
	 * @param request
	 * @return
	 */
	public static boolean hasValidRequestTimeStamp(final WrappedRequest request) {
		final String requestTimeString = getDateFromHeader(request);
		if (requestTimeString == null || requestTimeString.isEmpty()) {
			logger.error("请求时间戳无效.");
			return false;
		}
		 		
		final Calendar requestTime = Calendar.getInstance();
		requestTime.setTime(new Date(Long.parseLong(requestTimeString)));
		
		final int fiveMinutes = 60 * 5000;//定义客户端调用时间间隔不能超过5分钟
				
		final boolean inRange = requestTime.before(cal(Calendar.MILLISECOND, fiveMinutes)) && requestTime.after(cal(Calendar.MILLISECOND, -fiveMinutes));
		
		if (!inRange) {
			logger.warn("时间戳不符.Server: " + new Date().getTime()+ ". Request: " + requestTimeString + ".");
		}

		return inRange;
	}
	
	/**
	 * create签名信息内容.
	 * @param request
	 * @return
	 */
	public static String createSignatureBase(final WrappedRequest request) {
		StringBuilder sb = new StringBuilder(request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort())
								.append(request.getRequestURI());
		return createSignatureBase(request.getMethod(),	request.getHeader(HmacAttributes.X_HMAC_AUTH_DATE),sb.toString() ,request.getBody());
	}
	
	public static String createSignatureBase(final String method,final String dateHeader, final String requestUri, final String body) {
		final StringBuilder builder = new StringBuilder();

		builder.append(method).append("\n");
		builder.append(dateHeader).append("\n");
		builder.append(requestUri).append("\n");
		try {
			if(ExStringUtils.isNotBlank(body)) builder.append(EncryptUtils.MD5Encode(body).trim());
		} catch (NoSuchAlgorithmException e) {
			logger.error("MD5加密出错：{}",e);
		}

		return builder.toString();
	}

	/**
	 * 创建请求签名.
	 * @param method
	 * @param dateHeader
	 * @param requestUri
	 * @param body
	 * @param secretKey
	 * @return
	 */
	public static String createRequestSignature(final String method,final String dateHeader, final String requestUri,final String body, final String secretKey) {
		try {			
			final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(),DEFAULT_METHOD);
			final Mac mac = Mac.getInstance(DEFAULT_METHOD);
			mac.init(keySpec);
			final String signatureBase = createSignatureBase(method,dateHeader, requestUri, body);
			final byte[] result = mac.doFinal(signatureBase.getBytes());
			return encodeBase64WithoutLinefeed(result);

		} catch (final Exception e) {
			throw new RuntimeException("不可能的异常.", e);
		}
	}
	
	public static String createRequestSignature(final WrappedRequest request,final String secretKey){
		try {
			final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), DEFAULT_METHOD);
			final Mac mac = Mac.getInstance(DEFAULT_METHOD);
			mac.init(keySpec);
			final String signatureBase = createSignatureBase(request);
			final byte[] result = mac.doFinal(signatureBase.getBytes());
			return encodeBase64WithoutLinefeed(result);
		} catch (final Exception e) {
			throw new RuntimeException("不可能的异常.", e);
		}
	}
	
	/**
	 * Base64加密.
	 * @param result
	 * @return
	 */
	protected static String encodeBase64WithoutLinefeed(byte[] result) {
		return EncryptUtils.base64Encode(result).trim();
	}

	public static boolean hasSignature(final HttpServletRequest request) {
		return request.getHeader(HmacAttributes.X_HMAC_AUTH_SIGNATURE) != null;
	}

	public static String getSignature(final HttpServletRequest request) {
		return request.getHeader(HmacAttributes.X_HMAC_AUTH_SIGNATURE);
	}

	public static String getDateFromHeader(final HttpServletRequest request) {
		final String header = request.getHeader(HmacAttributes.X_HMAC_AUTH_DATE);
		
		return ExStringUtils.defaultIfEmpty(header, "");
	}
	
	 private static Calendar cal(int field,int amount){
		final Calendar c = Calendar.getInstance(); 
		c.add(field, amount);
		return c;
	}

}
